#include <windows.h>
#include <PipeLib.h>
#include "Pipe.h"
#include "Util.h"

#ifdef UNICODE
extern "C" BOOL GetStdioPathW(int, wchar_t*, DWORD*);
extern "C" BOOL SetStdioPathW(int, const wchar_t*);
#define GetStdioPath(fd, path, len) GetStdioPathW((fd), (path), (len))
#define SetStdioPath(fd, path) SetStdioPathW((fd), (path))
#else
extern "C" BOOL GetStdioPathA(int, char*, DWORD*);
extern "C" BOOL SetStdioPathA(int, const char*);
#define GetStdioPath(fd, path, len) GetStdioPathA((fd), (path), (len))
#define SetStdioPath(fd, path) SetStdioPathA((fd), (path))
#endif

WINAPI
Pipe::Pipe()
{
    int i;

    for (i = 0; i < 3; ++i) {
        m_hRead[i]  = INVALID_HANDLE_VALUE;
        m_hWrite[i] = INVALID_HANDLE_VALUE;
    }

    ZeroMemory(&m_childProcessInfo, sizeof(m_childProcessInfo));
    m_childProcessInfo.hThread  = INVALID_HANDLE_VALUE;
    m_childProcessInfo.hProcess = INVALID_HANDLE_VALUE;

    m_openned = FALSE;
}


BOOL
Pipe::avail(int fd, DWORD& bytes)
{
#if 0
    char buf[16];
    BOOL bRet;
    DWORD bytesRead, bytesTotal, bytesLeft;

    bRet = PeekNamedPipe(m_hRead[fd], buf, sizeof(buf),
                         &bytesRead, &bytesTotal, &bytesLeft);
#else
    BOOL bRet;
    DWORD bytesTotal;

    bRet = PeekNamedPipe(m_hRead[fd], NULL, 0, NULL, &bytesTotal, NULL);
#endif
    if (! bRet) {
        OutputLastError(TEXT("Pipe::avail"));
    } else {
        bytes = bytesTotal;
    }

    return bRet;
}


BOOL
Pipe::readAvail(DWORD& bytes)
{
    return avail(1, bytes);
}


BOOL
Pipe::readErrorAvail(DWORD& bytes)
{
    return avail(2, bytes);
}


BOOL
Pipe::readFile(int fd, char* buf, DWORD len, DWORD& bytesRead)
{
    BOOL bRet;

    if (! m_openned) {
        OutputDebugString(TEXT("not openned"));
        return FALSE;
    }

#if 0       // useless for PipeLib
    bRet = WaitForSingleObject(m_hRead[fd], INFINITE);
    switch (bRet) {
    case WAIT_OBJECT_0:
        printf("bRet = %d\n", bRet);
        break;
    case WAIT_ABANDONED:
        printf("bRet = %d\n", bRet);
        break;
    case WAIT_TIMEOUT:
        printf("bRet = %d\n", bRet);
        break;
    case WAIT_FAILED:
        printf("bRet = %d\n", bRet);
        break;
    default:
        printf("bRet = %d\n", bRet);
        break;
    }
#endif

    bRet = ReadFile(m_hRead[fd], buf, len, &bytesRead, FALSE);
    if (! bRet) {
        OutputLastError(TEXT("Pipe::read"));
        OutputDebugString(fd == 1 ? TEXT("stdout: broken pipe") : TEXT("stderr: broken pipe"));
        return FALSE;
    }

    return TRUE;
}


BOOL
Pipe::read(char* buf, DWORD len, DWORD& bytesRead)
{
    return readFile(1, buf, len, bytesRead);
}


BOOL
Pipe::readError(char* buf, DWORD len, DWORD& bytesRead)
{
    return readFile(2, buf, len, bytesRead);
}


BOOL
Pipe::write(const char* buf, DWORD len, DWORD& byteWritten)
{
    BOOL bRet;

    if (! m_openned) {
        OutputDebugString(TEXT("not openned"));
        return FALSE;
    }

    bRet = WriteFile(m_hWrite[0], buf, len, &byteWritten, NULL);
    if (! bRet) {
        OutputLastError(TEXT("Pipe::write"));
        OutputDebugString(TEXT("stdin: broken pipe"));
        return FALSE;
    }

    return TRUE;
}


BOOL
Pipe::open(TCHAR* program, TCHAR* args)
{
    TCHAR old_paths[3][MAX_PATH];
    TCHAR devname[MAX_PATH];
    BOOL bRet;
    DWORD dwLen;
    int i;

    for (i = 0; i < 3; ++i) {
        if (! CreatePipe(&m_hRead[i], &m_hWrite[i], NULL, 0))
            goto L_error;

        GetPipeName(m_hRead[i], devname);
        dwLen = MAX_PATH;
        GetStdioPathW(i, old_paths[i], &dwLen);
        SetStdioPathW(i, devname);
    }

    bRet = CreateProcess(program, args, NULL, NULL, FALSE, 0,
                         NULL, NULL, NULL, &m_childProcessInfo);

    if (! bRet)
        goto L_error;

    for (i = 0; i < 3; ++i)
        SetStdioPath(i, old_paths[i]);

    m_openned = TRUE;

    return TRUE;

L_error:
    close();
    return FALSE;
}


void
Pipe::close()
{
    int i;

    for (i = 0; i < 3; ++i) {
        if (INVALID_HANDLE_VALUE != m_hRead[i]) {
            CloseHandle(m_hRead[i]);
            m_hRead[i] = INVALID_HANDLE_VALUE;
        }

        if (INVALID_HANDLE_VALUE != m_hWrite[i]) {
            CloseHandle(m_hWrite[i]);
            m_hWrite[i] = INVALID_HANDLE_VALUE;
        }
    }

    if (INVALID_HANDLE_VALUE != m_childProcessInfo.hThread)
        CloseHandle(m_childProcessInfo.hThread);

    if (INVALID_HANDLE_VALUE != m_childProcessInfo.hProcess) {
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_childProcessInfo.hProcess, 2000))
            TerminateProcess(m_childProcessInfo.hProcess, 0);
        CloseHandle(m_childProcessInfo.hProcess);
    }

    ZeroMemory(&m_childProcessInfo, sizeof(m_childProcessInfo));
    m_childProcessInfo.hThread  = INVALID_HANDLE_VALUE;
    m_childProcessInfo.hProcess = INVALID_HANDLE_VALUE;

    m_openned = FALSE;
}

